using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Resources;
using System.Reflection;
using System.Threading;
using Microsoft.BizTalk.Adapter.Framework;

namespace Microsoft.Samples.BizTalk.Adapter.Tcp.Config
{
    /// <summary>
    ///     This class implements the functionality required for design time configuration of the TCP adapter.
    /// </summary>
    /// <author>
    ///     Paul Brown
    /// </author>
    public class AdapterManagement : IAdapterConfig, IAdapterConfigValidation
    {
        #region Private Constants

        private const string        TcpUriScheme = "TCP://";
        private const string        DeadMessageFolder = @"Microsoft\BizTalk Server\TcpAdapter\DeadMessages";

        #endregion

        #region Private Instance Members

        private ResourceManager     _resMgr;        // The resource manager used to access localized resources

        #endregion

        #region Constructor

        /// <summary>
        ///     This is the default constructor for the class.
        /// </summary>
        public AdapterManagement()
        {
            // Initialize members
            this._resMgr = new ResourceManager(ResourceConstants.BaseName, Assembly.GetExecutingAssembly());
        }

        #endregion

        #region IAdapterConfig Members

        /// <summary>
        ///     This method gets the appropriate configuration schema from the manifest resources based on the config type.
        /// </summary>
        public string GetConfigSchema(ConfigType configType)
        {
            switch (configType)
            {
                case ConfigType.ReceiveHandler:
                    return (this.LocalizeSchema(this.GetSchemaFromResource(ResourceConstants.ReceiveHandlerSchema)));

                case ConfigType.ReceiveLocation:
                    return (this.LocalizeSchema(this.GetSchemaFromResource(ResourceConstants.ReceiveLocationSchema)));

                case ConfigType.TransmitHandler:
                    return (this.LocalizeSchema(this.GetSchemaFromResource(ResourceConstants.TransmitHandlerSchema)));

                case ConfigType.TransmitLocation:
                    return (this.LocalizeSchema(this.GetSchemaFromResource(ResourceConstants.TransmitLocationSchema)));

                default:
                    return null;
            }
        }

        /// <summary>
        ///     This method returns the location of schema files that need to be imported.
        /// </summary>
        public Result GetSchema(string uri, string namespaceName, out string fileLocation)
        {
            // We have no external schema files, return null and continue
            fileLocation = null;
            return (Result.Continue);
        }

        #endregion

        #region IAdapterConfigValidation Members

        /// <summary>
        ///     This method validates the configuration for the different config types before the adapter changes are saved.
        /// </summary>
        public string ValidateConfiguration(ConfigType configType, string configuration)
        {
            string validConfiguration = null;
            switch (configType)
            {
                case ConfigType.ReceiveHandler :

                    validConfiguration = this.ValidateReceiveHandlerConfiguration(configuration);
                    break;

                case ConfigType.ReceiveLocation :

                    validConfiguration = this.ValidateReceiveLocationConfiguration(configuration);
                    break;

                case ConfigType.TransmitHandler :

                    validConfiguration = this.ValidateTransmitHandlerConfiguration(configuration);
                    break;

                case ConfigType.TransmitLocation :

                    validConfiguration = this.ValidateTransmitLocationConfiguration(configuration);
                    break;
            }

            // Return valid configuration XML
            return (validConfiguration);
        }

        #endregion

        #region Private Instance Methods

        /// <summary>
        ///     This method validates the configuration for the receive handler.
        /// </summary>
        private string ValidateReceiveHandlerConfiguration(string configuration)
        {
            // Local configuration
            XmlDocument configXml = new XmlDocument();
            configXml.LoadXml(configuration);

            // Dead Message Folder
            XmlNode node = configXml.SelectSingleNode("/Config/deadMessageFolder");
            if (node == null) throw new ArgumentNullException("/Config/deadMessageFolder");
            string deadMessageFolder = node.InnerText;

            if (string.IsNullOrEmpty(deadMessageFolder))
            {
                // Return user temporary folder
                node.InnerText = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + @"\" + AdapterManagement.DeadMessageFolder;
            }

            // Return updated XML
            return (configXml.OuterXml);
        }

        /// <summary>
        ///     This method validates the configuration for the receive location.
        /// </summary>
        private string ValidateReceiveLocationConfiguration(string configuration)
        {
            // Variables
            int listenPort;

            // Local configuration
            XmlDocument configXml = new XmlDocument();
            configXml.LoadXml(configuration);

            // TCP Listen Port
            XmlNode node = configXml.SelectSingleNode("/Config/tcpListenPort");
            if (node == null) throw new ArgumentNullException("tcpListenPort");
            listenPort = int.Parse(node.InnerText);

            // Data Encoding
            this.ValidateDataEncoding(configXml);

            // Frame Encoding
            this.ValidateFrameEncoding(configXml);

            // Check for uri node
            XmlNode uriNode = configXml.SelectSingleNode("/Config/uri");

            if (uriNode == null)
            {
                // Add uri node
                uriNode = configXml.CreateNode(XmlNodeType.Element, "uri", configXml.DocumentElement.NamespaceURI);
                configXml.DocumentElement.AppendChild(uriNode);
            }

            // Set address
            uriNode.InnerText = string.Format("{0}{1}:{2}", AdapterManagement.TcpUriScheme, System.Net.Dns.GetHostName(), listenPort);

            // Return updated XML
            return (configXml.OuterXml);
        }

        /// <summary>
        ///     This method validates the configuration for the transmit handler.
        /// </summary>
        private string ValidateTransmitHandlerConfiguration(string configuration)
        {
            // Local configuration
            XmlDocument configXml = new XmlDocument();
            configXml.LoadXml(configuration);

            // Dead Message Folder
            XmlNode node = configXml.SelectSingleNode("/Config/deadMessageFolder");
            if (node == null) throw new ArgumentNullException("/Config/deadMessageFolder");
            string deadMessageFolder = node.InnerText;

            if (string.IsNullOrEmpty(deadMessageFolder))
            {
                // Return user temporary folder
                node.InnerText = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + @"\" + AdapterManagement.DeadMessageFolder;
            }

            // Return updated XML
            return (configXml.OuterXml);
        }

        /// <summary>
        ///     This method validates the configuration for the transmit location.
        /// </summary>
        private string ValidateTransmitLocationConfiguration(string configuration)
        {
            // Variables
            int     endpointPort;
            string  endpointAddress = string.Empty;
            string  bizTalkParty = string.Empty;
            bool    supportsDuplex;

            // Local configuration
            XmlDocument configXml = new XmlDocument();
            configXml.LoadXml(configuration);

            // Endpoint Address
            XmlNode node = configXml.SelectSingleNode("/Config/tcpEndpointAddress");
            if (node != null) endpointAddress = node.InnerText;

            // Endpoint Port
            node = configXml.SelectSingleNode("/Config/tcpEndpointPort");
            if (node == null) throw new ArgumentNullException("tcpEndpointPort");
            endpointPort = int.Parse(node.InnerText);

            // Supports Duplex
            node = configXml.SelectSingleNode("/Config/tcpSupportsDuplex");
            if (node == null) throw new ArgumentNullException("tcpSupportsDuplex");
            supportsDuplex = bool.Parse(node.InnerText);

            // Party Endpoint
            node = configXml.SelectSingleNode("/Config/tcpPartyEndpoint");
            if (node != null) bizTalkParty = node.InnerText;

            // Validate
            if (supportsDuplex && bizTalkParty == string.Empty)
            {
                // Must specify a BizTalk Party name if send location supports duplex connections
                throw new Exception(this._resMgr.GetString(ResourceConstants.MustSpecifyBizTalkPartyIfDuplex));
            }

            if (!supportsDuplex && endpointAddress == string.Empty)
            {
                // Must specify an endpoint address if send location is not supporting duplex connections
                throw new Exception(this._resMgr.GetString(ResourceConstants.MustSpecifyEndpointAddressIfNotDuplex));
            }

            // Validate data encoding
            this.ValidateDataEncoding(configXml);

            // Validate frame encoding
            this.ValidateFrameEncoding(configXml);

            // Check for uri node
            XmlNode uriNode = configXml.SelectSingleNode("/Config/uri");

            if (uriNode == null)
            {
                // Add uri node
                uriNode = configXml.CreateNode(XmlNodeType.Element, "uri", configXml.DocumentElement.NamespaceURI);
                configXml.DocumentElement.AppendChild(uriNode);
            }

            if (supportsDuplex)
            {
                // Set address as duplex
                uriNode.InnerText = string.Format("{0}{1}", AdapterManagement.TcpUriScheme, bizTalkParty);
            }
            else
            {
                // Set address as normal
                uriNode.InnerText = string.Format("{0}{1}:{2}", AdapterManagement.TcpUriScheme, endpointAddress, endpointPort);
            }

            // Return updated XML
            return (configXml.OuterXml);
        }

        /// <summary>
        ///     This method validates the data encoding value in the configuration.
        /// </summary>
        private void ValidateDataEncoding(XmlDocument configXml)
        {
            XmlNode node = configXml.SelectSingleNode("/Config/dataEncoding");
            if (node == null) throw new ArgumentNullException("dataEncoding");
            DataEncodingTypes encodingType = (DataEncodingTypes)Enum.Parse(typeof(DataEncodingTypes), node.InnerText);

            if (encodingType == DataEncodingTypes.CustomIdentifier || encodingType == DataEncodingTypes.CustomName)
            {
                // Get custom value
                node = configXml.SelectSingleNode("/Config/dataEncodingCustomValue");
                if (node == null || node.InnerText == string.Empty) throw new ArgumentNullException(this._resMgr.GetString(ResourceConstants.MustSpecifyCustomEncodingValue));

                if (encodingType == DataEncodingTypes.CustomIdentifier)
                {
                    try
                    {
                        // Check to see if it is numeric
                        int customValue = int.Parse(node.InnerText);
                    }
                    catch
                    {
                        throw new ArgumentException(this._resMgr.GetString(ResourceConstants.MustSpecifyCustomValueAsNumeric));
                    }
                }
            }
        }

        /// <summary>
        ///     This method validates the frame encoding value in the configuration.
        /// </summary>
        private void ValidateFrameEncoding(XmlDocument configXml)
        {
            XmlNode node = configXml.SelectSingleNode("/Config/frameEncoding");
            if (node == null) throw new ArgumentNullException("frameEncoding");
            DataEncodingTypes encodingType = (DataEncodingTypes)Enum.Parse(typeof(DataEncodingTypes), node.InnerText);

            if (encodingType == DataEncodingTypes.CustomIdentifier || encodingType == DataEncodingTypes.CustomName)
            {
                // Get custom value
                node = configXml.SelectSingleNode("/Config/frameEncodingCustomValue");
                if (node == null || node.InnerText == string.Empty) throw new ArgumentNullException(this._resMgr.GetString(ResourceConstants.MustSpecifyCustomEncodingValue));

                if (encodingType == DataEncodingTypes.CustomIdentifier)
                {
                    try
                    {
                        // Check to see if it is numeric
                        int customValue = int.Parse(node.InnerText);
                    }
                    catch
                    {
                        throw new ArgumentException(this._resMgr.GetString(ResourceConstants.MustSpecifyCustomValueAsNumeric));
                    }
                }
            }
            else if (encodingType == DataEncodingTypes.Binary)
            {
                // Check to see if the frame start and end values can be converted
                node = configXml.SelectSingleNode("/Config/frameStartDelimiter");
                if (node == null) throw new ArgumentNullException("frameStartDelimiter");
                string[] frameStartHexValues = node.InnerText.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                try
                {
                    foreach (string hex in frameStartHexValues)
                    {
                        byte convertedValue = Convert.ToByte(hex, 16);
                    }
                }
                catch
                {
                    throw new ArgumentException(this._resMgr.GetString(ResourceConstants.MustSpecifyBinaryFormatCorrectly));
                }

                node = configXml.SelectSingleNode("/Config/frameEndDelimiter");
                if (node == null) throw new ArgumentNullException("frameEndDelimiter");
                string[] frameEndHexValues = node.InnerText.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                try
                {
                    foreach (string hex in frameStartHexValues)
                    {
                        byte convertedValue = Convert.ToByte(hex, 16);
                    }
                }
                catch
                {
                    throw new ArgumentException(this._resMgr.GetString(ResourceConstants.MustSpecifyBinaryFormatCorrectly));
                }
            }
        }

        /// <summary>
        ///     This method retrieves the schema from the manifest resources.
        /// </summary>
        private string GetSchemaFromResource(string name)
        {
            Assembly assembly = this.GetType().Assembly;
            string schema = null;

            // Get schema from manifest
            using (Stream stream = assembly.GetManifestResourceStream(name))
            {
                try
                {
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        try
                        {
                            // Get schema from reader
                            schema = reader.ReadToEnd();
                        }
                        finally
                        {
                            // Clean up
                            reader.Close();
                        }
                    }
                }
                finally
                {
                    // Clean up
                    stream.Close();
                }
            }

            // Return schema
            return (schema);
        }

        /// <summary>
        ///     This method updates the schema with the strings marked as localizable replaced with the values returned from the
        ///     resource assembly.
        /// </summary>
        private string LocalizeSchema(string schema)
        {
            // Load schema
            XmlDocument document = new XmlDocument();
            document.LoadXml(schema);

            // Find all nodes that have a _locID attribute
            XmlNodeList nodes = document.SelectNodes("/descendant::*[@_locID]");
            foreach (XmlNode node in nodes)
            {
                // Get attribute value and use it as a key to a string in the resource assembly
                string locID = node.Attributes["_locID"].Value;
                node.InnerText = this._resMgr.GetString(locID);
            }

            // Write the schema back out as a string and return it
            string localizedSchema = null;
            using (StringWriter writer = new StringWriter())
            {
                try
                {
                    document.WriteTo(new XmlTextWriter(writer));
                    localizedSchema = writer.ToString();
                }
                finally
                {
                    writer.Close();
                }
            }

            // Return schema
            return (localizedSchema);
        }

        #endregion
    }
}
